{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "623f6923-29e5-4011-bc33-3ed61d57615e",
   "metadata": {},
   "outputs": [],
   "source": [
    "import tkinter as tk\n",
    "from tkinter import filedialog, messagebox\n",
    "from PIL import Image, ImageTk\n",
    "import os\n",
    "import numpy as np\n",
    "import image_channels\n",
    "import image_io\n",
    "import image_transform\n",
    "import config\n",
    "import sys\n",
    "from image_utils import language_data, logging\n",
    "import image_utils as utils\n",
    "import subprocess\n",
    "\n",
    "class ImageProcessor:\n",
    "    def __init__(self, master):\n",
    "        \"\"\"初始化ImageProcessor类的实例\"\"\"\n",
    "        self.master = master  # 主窗口\n",
    "        \n",
    "        # 先初始化 current_language\n",
    "        self.current_language = language_data[\"current_language\"]  # 默认语言通过JSON文件直接设置\n",
    "        \n",
    "        # 确保所有必要的翻译键存在\n",
    "        self._ensure_translations()\n",
    "        \n",
    "        master.title(language_data[self.current_language]['app_title'])  # 设置初始窗口标题\n",
    "\n",
    "        self.image = None  # 用于存储当前图像的numpy数组\n",
    "        self.image_path = \"\"  # 用于存储当前图像的文件路径\n",
    "        self.rect_start_x = None  # 用于存储裁剪矩形框起始点的x坐标\n",
    "        self.rect_start_y = None  # 用于存储裁剪矩形框起始点的y坐标\n",
    "        self.rect_end_x = None  # 用于存储裁剪矩形框结束点的x坐标\n",
    "        self.rect_end_y = None  # 用于存储裁剪矩形框结束点的y坐标\n",
    "        self.is_drawing = False  # 用于标记是否正在绘制裁剪矩形框\n",
    "        self.current_rect = None  # 用于存储当前绘制的裁剪矩形框对象\n",
    "        self.diff_window = None  # 新增差分窗口引用\n",
    "        self.shift_amount = 0     # 新增平移量\n",
    "        self.threshold_window = None  # 阈值窗口引用\n",
    "        self.mask_window = None   # 掩模窗口引用\n",
    "\n",
    "        self.create_widgets()  # 创建GUI组件\n",
    "    \n",
    "    def _ensure_translations(self):\n",
    "        \"\"\"确保所有必要的翻译键存在\"\"\"\n",
    "        # 定义需要的翻译键及其默认值\n",
    "        required_keys = {\n",
    "            \"adjust_brightness\": {\"zh\": \"调整亮度\", \"en\": \"Adjust Brightness\"},\n",
    "            \"brightness\": {\"zh\": \"亮度\", \"en\": \"Brightness\"},\n",
    "            \"brightness_adjustment\": {\"zh\": \"亮度调节\", \"en\": \"Brightness Adjustment\"},\n",
    "            \"apply\": {\"zh\": \"应用\", \"en\": \"Apply\"},\n",
    "            \"cancel\": {\"zh\": \"取消\", \"en\": \"Cancel\"},\n",
    "            \"no_image_loaded\": {\"zh\": \"没有加载图像\", \"en\": \"No image loaded\"},\n",
    "            \"edge\": {\"zh\": \"边缘\", \"en\": \"Edge\"},\n",
    "            \"move_diff\": {\"zh\": \"移动差分\", \"en\": \"Move Diff\"},\n",
    "            \"shift_amount\": {\"zh\": \"平移量\", \"en\": \"Shift Amount\"},\n",
    "            \"move_diff_title\": {\"zh\": \"移动差分边缘检测\", \"en\": \"Move Diff Edge Detection\"},\n",
    "            \"need_gray_image\": {\"zh\": \"需要先载入灰度图像\", \"en\": \"Need to load grayscale image first\"},\n",
    "            \"no_reset_image\": {\"zh\": \"没有可重置的图像\", \"en\": \"No image to reset\"},\n",
    "            \"no_image_to_be_cropped\": {\"zh\": \"没有可裁剪的图像\", \"en\": \"No image to be cropped\"},\n",
    "            \"crop_failed\": {\"zh\": \"裁剪失败\", \"en\": \"Crop failed\"},\n",
    "            \"no_crop_area\": {\"zh\": \"没有选择裁剪区域\", \"en\": \"No crop area selected\"},\n",
    "            \"error\": {\"zh\": \"错误\", \"en\": \"Error\"},\n",
    "            \"single_file_error\": {\"zh\": \"至少需要选择两个文件\", \"en\": \"At least two files are required\"},\n",
    "            \"single_channel_error\": {\"zh\": \"当前图像是单通道图像\", \"en\": \"Current image is single channel\"},\n",
    "            \"channel_title\": {\"zh\": \"通道 {}\", \"en\": \"Channel {}\"},\n",
    "            \"save_channel\": {\"zh\": \"保存通道\", \"en\": \"Save Channel\"},\n",
    "            \"channel_saved\": {\"zh\": \"通道 {} 已保存到 {}\", \"en\": \"Channel {} saved to {}\"},\n",
    "            \"details\": {\"zh\": \"图像详情\", \"en\": \"Image Details\"},\n",
    "            \"info_str\": {\"zh\": \"文件名: {}\\n总像素: {}\\n尺寸: {}\\n通道数: {}\\n数据类型: {}\", \n",
    "                       \"en\": \"Filename: {}\\nTotal Pixels: {}\\nDimensions: {}\\nChannels: {}\\nData Type: {}\"},\n",
    "            \"success\": {\"zh\": \"成功\", \"en\": \"Success\"},\n",
    "            \"no_image_to_be_saved\": {\"zh\": \"没有可保存的图像\", \"en\": \"No image to be saved\"},\n",
    "            \"confirm_restart\": {\"zh\": \"确认重启\", \"en\": \"Confirm Restart\"},\n",
    "            \"restart_warning\": {\"zh\": \"更改语言需要重启应用程序。是否继续？\", \"en\": \"Changing language requires restarting the application. Continue?\"},\n",
    "            \"threshold\": {\"zh\": \"阈值\", \"en\": \"Threshold\"},\n",
    "            \"threshold_processing\": {\"zh\": \"阈值处理\", \"en\": \"Threshold Processing\"},\n",
    "            \"threshold_value\": {\"zh\": \"阈值\", \"en\": \"Threshold Value\"},\n",
    "            \"binary_threshold\": {\"zh\": \"二值阈值\", \"en\": \"Binary Threshold\"},\n",
    "            \"otsu_threshold\": {\"zh\": \"Otsu阈值\", \"en\": \"Otsu Threshold\"},\n",
    "            \"adaptive_threshold\": {\"zh\": \"自适应阈值\", \"en\": \"Adaptive Threshold\"},\n",
    "            \"threshold_type\": {\"zh\": \"阈值类型\", \"en\": \"Threshold Type\"},\n",
    "            \"mask\": {\"zh\": \"掩模\", \"en\": \"Mask\"},  # 新增掩模翻译\n",
    "            \"red_mask\": {\"zh\": \"红色掩模\", \"en\": \"Red Mask\"},\n",
    "            \"green_mask\": {\"zh\": \"绿色掩模\", \"en\": \"Green Mask\"},\n",
    "            \"blue_mask\": {\"zh\": \"蓝色掩模\", \"en\": \"Blue Mask\"},\n",
    "        }\n",
    "        \n",
    "        # 确保每种语言都有这些键\n",
    "        for lang in [\"zh\", \"en\"]:\n",
    "            if lang not in language_data:\n",
    "                language_data[lang] = {}\n",
    "            \n",
    "            for key, translations in required_keys.items():\n",
    "                if key not in language_data[lang]:\n",
    "                    language_data[lang][key] = translations[lang]\n",
    "\n",
    "    def reset_image(self):\n",
    "        \"\"\"重置当前图像为原始状态\"\"\"\n",
    "        if not self.image_path:  # 如果没有图像路径,显示错误消息并返回\n",
    "            tk.messagebox.showinfo(language_data[self.current_language]['error'], \n",
    "                                  language_data[self.current_language]['no_reset_image'])\n",
    "            logging.error(\"没有图像可重置,重置操作已取消。\")  # 记录错误日志\n",
    "            return\n",
    "            \n",
    "        try:\n",
    "            # 重新加载原始图像\n",
    "            self.image = image_io.load_image(self.image_path)\n",
    "            self.display_image()  # 显示图像\n",
    "            logging.info(\"图像已重置为原始状态。\")  # 记录成功日志\n",
    "            \n",
    "            # 重置亮度滑块\n",
    "            if hasattr(self, 'brightness_scale'):\n",
    "                self.brightness_scale.set(100)\n",
    "                \n",
    "        except Exception as e:\n",
    "            tk.messagebox.showerror(language_data[self.current_language]['error'], f\"重置失败: {str(e)}\")\n",
    "            logging.error(f\"重置图像失败: {str(e)}\")\n",
    "\n",
    "    def crop_image(self):\n",
    "        \"\"\"裁剪当前图像\"\"\"\n",
    "        if self.image is None:\n",
    "            tk.messagebox.showinfo(language_data[self.current_language]['error'], \n",
    "                                  language_data[self.current_language]['no_image_to_be_cropped'])\n",
    "            logging.error(\"没有图像可裁剪,裁剪操作已取消。\")  # 记录错误日志\n",
    "            return  # 如果当前没有图像,直接返回\n",
    "\n",
    "        # 检查是否有绘制的裁剪区域\n",
    "        if hasattr(self, 'crop_region') and self.current_rect:\n",
    "            # 计算实际裁剪区域坐标(考虑图像缩放和偏移)\n",
    "            image_height, image_width = self.image.shape[:2]  # 获取图像的高度和宽度\n",
    "            canvas_width = self.canvas.winfo_width()  # 获取画布的宽度\n",
    "            canvas_height = self.canvas.winfo_height()  # 获取画布的高度\n",
    "            scale = min(canvas_width / image_width, canvas_height / image_height)  # 计算图像的缩放比例\n",
    "            offset_x = (canvas_width - image_width * scale) // 2  # 计算图像在画布上的水平偏移量\n",
    "            offset_y = (canvas_height - image_height * scale) // 2  # 计算图像在画布上的垂直偏移量\n",
    "\n",
    "            # 计算实际裁剪区域坐标\n",
    "            real_crop_region = (\n",
    "                int((self.crop_region[0] - offset_y) / scale),\n",
    "                int((self.crop_region[1] - offset_y) / scale),\n",
    "                int((self.crop_region[2] - offset_x) / scale),\n",
    "                int((self.crop_region[3] - offset_x) / scale)\n",
    "            )\n",
    "\n",
    "            # 调用裁剪函数\n",
    "            try:\n",
    "                cropped_image = image_transform.crop_image(self.image, real_crop_region)\n",
    "                if cropped_image is not None:\n",
    "                    self.image = cropped_image  # 将裁剪后的图像保存到self.image中\n",
    "                    self.display_image()  # 显示裁剪后的图像\n",
    "                    logging.info(\"图像裁剪成功。\")  # 记录成功日志\n",
    "\n",
    "                    # 清零裁剪框信息\n",
    "                    self.canvas.delete(self.current_rect)  # 删除画布上的裁剪矩形框\n",
    "                    self.current_rect = None  # 将当前裁剪矩形框对象设置为None\n",
    "                    if hasattr(self, 'crop_region'):\n",
    "                        delattr(self, 'crop_region')  # 删除存储裁剪区域坐标的属性\n",
    "                else:\n",
    "                    tk.messagebox.showinfo(language_data[self.current_language]['error'], \n",
    "                                          language_data[self.current_language]['crop_failed'])\n",
    "                    logging.error(\"裁剪操作失败。\")  # 记录错误日志\n",
    "            except Exception as e:\n",
    "                tk.messagebox.showerror(language_data[self.current_language]['error'], f\"裁剪错误: {str(e)}\")\n",
    "                logging.error(f\"裁剪操作失败: {str(e)}\")  # 记录错误日志\n",
    "        else:\n",
    "            tk.messagebox.showinfo(language_data[self.current_language]['error'], \n",
    "                                  language_data[self.current_language]['no_crop_area'])\n",
    "            logging.error(\"没有选择裁剪区域,裁剪操作已取消。\")  # 记录错误日志\n",
    "\n",
    "    def adjust_brightness(self):\n",
    "        \"\"\"显示亮度调节面板\"\"\"\n",
    "        if self.image is None:\n",
    "            tk.messagebox.showinfo(language_data[self.current_language]['error'], \n",
    "                                  language_data[self.current_language]['no_image_loaded'])\n",
    "            return\n",
    "            \n",
    "        # 创建亮度调节窗口\n",
    "        self.brightness_window = tk.Toplevel(self.master)\n",
    "        self.brightness_window.title(language_data[self.current_language]['brightness_adjustment'])\n",
    "        \n",
    "        # 创建亮度调节滑块\n",
    "        brightness_scale = tk.Scale(self.brightness_window, from_=0, to=200, orient=tk.HORIZONTAL,\n",
    "                                   label=language_data[self.current_language]['brightness'],\n",
    "                                   resolution=10, length=300)\n",
    "        brightness_scale.set(100)  # 默认值为100%\n",
    "        brightness_scale.pack(padx=20, pady=20)\n",
    "        \n",
    "        # 创建应用按钮\n",
    "        apply_button = tk.Button(self.brightness_window, \n",
    "                                text=language_data[self.current_language]['apply'],\n",
    "                                command=lambda: self.apply_brightness(brightness_scale.get()))\n",
    "        apply_button.pack(pady=10)\n",
    "        \n",
    "        # 创建取消按钮\n",
    "        cancel_button = tk.Button(self.brightness_window, \n",
    "                                 text=language_data[self.current_language]['cancel'],\n",
    "                                 command=self.brightness_window.destroy)\n",
    "        cancel_button.pack(pady=5)\n",
    "\n",
    "    def threshold_processing(self):\n",
    "        \"\"\"显示阈值处理面板\"\"\"\n",
    "        if self.image is None:\n",
    "            tk.messagebox.showinfo(language_data[self.current_language]['error'], \n",
    "                                  language_data[self.current_language]['no_image_loaded'])\n",
    "            return\n",
    "            \n",
    "        if len(self.image.shape) != 2:\n",
    "            tk.messagebox.showinfo(language_data[self.current_language]['error'], \n",
    "                                  language_data[self.current_language]['need_gray_image'])\n",
    "            return\n",
    "            \n",
    "        # 创建阈值处理窗口\n",
    "        self.threshold_window = tk.Toplevel(self.master)\n",
    "        self.threshold_window.title(language_data[self.current_language]['threshold_processing'])\n",
    "        \n",
    "        # 创建阈值类型选择\n",
    "        threshold_type_frame = tk.Frame(self.threshold_window)\n",
    "        threshold_type_frame.pack(padx=20, pady=10)\n",
    "        \n",
    "        tk.Label(threshold_type_frame, text=language_data[self.current_language]['threshold_type']).pack(side=tk.LEFT)\n",
    "        \n",
    "        self.threshold_type = tk.StringVar(value=\"binary\")\n",
    "        threshold_types = [\n",
    "            (\"Binary\", \"binary\"),\n",
    "            (\"Otsu\", \"otsu\"),\n",
    "            (\"Adaptive\", \"adaptive\")\n",
    "        ]\n",
    "        \n",
    "        for text, value in threshold_types:\n",
    "            tk.Radiobutton(threshold_type_frame, text=text, variable=self.threshold_type, value=value).pack(side=tk.LEFT, padx=10)\n",
    "        \n",
    "        # 创建阈值滑块\n",
    "        self.threshold_scale = tk.Scale(self.threshold_window, from_=0, to=255, orient=tk.HORIZONTAL,\n",
    "                                       label=language_data[self.current_language]['threshold_value'],\n",
    "                                       resolution=1, length=300)\n",
    "        self.threshold_scale.set(128)\n",
    "        self.threshold_scale.pack(padx=20, pady=10)\n",
    "        \n",
    "        # 创建应用按钮\n",
    "        apply_button = tk.Button(self.threshold_window, \n",
    "                                text=language_data[self.current_language]['apply'],\n",
    "                                command=self.apply_threshold)\n",
    "        apply_button.pack(pady=10)\n",
    "        \n",
    "        # 创建取消按钮\n",
    "        cancel_button = tk.Button(self.threshold_window, \n",
    "                                 text=language_data[self.current_language]['cancel'],\n",
    "                                 command=self.threshold_window.destroy)\n",
    "        cancel_button.pack(pady=5)\n",
    "\n",
    "    def apply_threshold(self):\n",
    "        \"\"\"应用阈值处理\"\"\"\n",
    "        if self.image is None or len(self.image.shape) != 2:\n",
    "            return\n",
    "            \n",
    "        threshold_type = self.threshold_type.get()\n",
    "        threshold_value = self.threshold_scale.get()\n",
    "        \n",
    "        if threshold_type == \"binary\":\n",
    "            # 二值阈值处理\n",
    "            thresholded_image = np.zeros_like(self.image)\n",
    "            thresholded_image[self.image > threshold_value] = 255\n",
    "        elif threshold_type == \"otsu\":\n",
    "            # Otsu阈值处理\n",
    "            # 计算直方图\n",
    "            hist, bins = np.histogram(self.image.flatten(), 256, [0, 256])\n",
    "            # 计算概率分布\n",
    "            p = hist / hist.sum()\n",
    "            # 初始化最佳阈值和最大类间方差\n",
    "            best_threshold = 0\n",
    "            max_var = 0\n",
    "            # 遍历所有可能的阈值\n",
    "            for t in range(1, 256):\n",
    "                # 计算前景和背景的概率\n",
    "                w0 = p[:t].sum()\n",
    "                w1 = p[t:].sum()\n",
    "                if w0 == 0 or w1 == 0:\n",
    "                    continue\n",
    "                # 计算前景和背景的均值\n",
    "                mu0 = np.sum(np.arange(t) * p[:t]) / w0\n",
    "                mu1 = np.sum(np.arange(t, 256) * p[t:]) / w1\n",
    "                # 计算类间方差\n",
    "                var = w0 * w1 * (mu0 - mu1) ** 2\n",
    "                # 更新最佳阈值\n",
    "                if var > max_var:\n",
    "                    max_var = var\n",
    "                    best_threshold = t\n",
    "            # 应用最佳阈值\n",
    "            thresholded_image = np.zeros_like(self.image)\n",
    "            thresholded_image[self.image > best_threshold] = 255\n",
    "        elif threshold_type == \"adaptive\":\n",
    "            # 自适应阈值处理\n",
    "            thresholded_image = np.zeros_like(self.image)\n",
    "            block_size = 11  # 块大小\n",
    "            c = 2  # 常量，从局部均值中减去\n",
    "            height, width = self.image.shape\n",
    "            # 处理每个像素\n",
    "            for i in range(height):\n",
    "                for j in range(width):\n",
    "                    # 计算局部区域\n",
    "                    i_min = max(0, i - block_size//2)\n",
    "                    i_max = min(height, i + block_size//2 + 1)\n",
    "                    j_min = max(0, j - block_size//2)\n",
    "                    j_max = min(width, j + block_size//2 + 1)\n",
    "                    # 计算局部均值\n",
    "                    local_mean = np.mean(self.image[i_min:i_max, j_min:j_max])\n",
    "                    # 应用阈值\n",
    "                    thresholded_image[i, j] = 255 if self.image[i, j] > local_mean - c else 0\n",
    "        \n",
    "        # 更新当前图像并显示\n",
    "        self.image = thresholded_image\n",
    "        self.display_image()\n",
    "        \n",
    "        # 关闭阈值窗口\n",
    "        if hasattr(self, 'threshold_window'):\n",
    "            self.threshold_window.destroy()\n",
    "\n",
    "    def create_widgets(self):\n",
    "        \"\"\"创建GUI组件\"\"\"\n",
    "        # 创建菜单栏\n",
    "        menubar = tk.Menu(self.master)\n",
    "        self.master.config(menu=menubar)\n",
    "\n",
    "        # 创建文件菜单\n",
    "        file_menu = tk.Menu(menubar)\n",
    "        file_menu.add_command(label=language_data[self.current_language]['open_original'], command=self.Open_as_original)\n",
    "        file_menu.add_command(label=language_data[self.current_language]['open_grayscale'], command=self.Open_as_gray)\n",
    "        file_menu.add_command(label=language_data[self.current_language]['merge_channels'], command=self.merge_channels)\n",
    "        file_menu.add_command(label=language_data[self.current_language]['save_image'], command=self.save_image)\n",
    "        file_menu.add_separator()\n",
    "        \n",
    "        # 创建语言子菜单\n",
    "        language_menu = tk.Menu(file_menu)\n",
    "        self.language_var = tk.StringVar(value=self.current_language)\n",
    "        language_menu.add_radiobutton(label=\"中文\", variable=self.language_var, value=\"zh\", command=self.change_language)\n",
    "        language_menu.add_radiobutton(label=\"English\", variable=self.language_var, value=\"en\", command=self.change_language)\n",
    "        file_menu.add_cascade(label=language_data[self.current_language]['language'], menu=language_menu)\n",
    "        \n",
    "        file_menu.add_command(label=language_data[self.current_language]['exit'], command=self.master.quit)\n",
    "        menubar.add_cascade(label=language_data[self.current_language]['file'], menu=file_menu)\n",
    "\n",
    "        # 创建信息菜单\n",
    "        info_menu = tk.Menu(menubar)\n",
    "        info_menu.add_command(label=language_data[self.current_language]['details'], command=self.show_image_details)\n",
    "        menubar.add_cascade(label=language_data[self.current_language]['info'], menu=info_menu)\n",
    "\n",
    "        # 创建编辑菜单\n",
    "        edit_menu = tk.Menu(menubar)\n",
    "        edit_menu.add_command(label=language_data[self.current_language]['crop'], command=self.crop_image)\n",
    "        edit_menu.add_command(label=language_data[self.current_language]['reset'], command=self.reset_image)\n",
    "        # 添加改变亮度的功能\n",
    "        edit_menu.add_command(label=language_data[self.current_language]['adjust_brightness'], command=self.adjust_brightness)\n",
    "        menubar.add_cascade(label=language_data[self.current_language]['edit'], menu=edit_menu)\n",
    "\n",
    "        # 创建阈值菜单(主菜单顶层)\n",
    "        threshold_menu = tk.Menu(menubar)\n",
    "        threshold_menu.add_command(label=language_data[self.current_language]['threshold'], command=self.threshold_processing)\n",
    "        menubar.add_cascade(label=language_data[self.current_language]['threshold'], menu=threshold_menu)\n",
    "\n",
    "        # 创建图像分割菜单\n",
    "        split_menu = tk.Menu(menubar)\n",
    "        menubar.add_cascade(label=language_data[self.current_language]['split'], menu=split_menu)\n",
    "        split_menu.add_command(label=language_data[self.current_language]['split_channels'], command=self.split_image_channels)\n",
    "\n",
    "        # 创建边缘菜单\n",
    "        edge_menu = tk.Menu(menubar)\n",
    "        menubar.add_cascade(label=language_data[self.current_language]['edge'], menu=edge_menu)\n",
    "        edge_menu.add_command(label=language_data[self.current_language]['move_diff'], command=self.show_move_diff)\n",
    "\n",
    "        # 创建掩模菜单(主菜单顶层)\n",
    "        mask_menu = tk.Menu(menubar)\n",
    "        mask_submenu = tk.Menu(mask_menu, tearoff=0)\n",
    "        mask_submenu.add_command(label=language_data[self.current_language]['red_mask'], command=lambda: self.create_mask(0))\n",
    "        mask_submenu.add_command(label=language_data[self.current_language]['green_mask'], command=lambda: self.create_mask(1))\n",
    "        mask_submenu.add_command(label=language_data[self.current_language]['blue_mask'], command=lambda: self.create_mask(2))\n",
    "        mask_menu.add_cascade(label=language_data[self.current_language]['mask'], menu=mask_submenu)\n",
    "        menubar.add_cascade(label=language_data[self.current_language]['mask'], menu=mask_menu)\n",
    "\n",
    "        # 创建图像显示区域\n",
    "        self.canvas = tk.Canvas(self.master, width=600, height=400)\n",
    "        self.canvas.pack()\n",
    "\n",
    "        self.canvas.bind(\"<Button-1>\", self.on_mouse_down)\n",
    "        self.canvas.bind(\"<B1-Motion>\", self.on_mouse_move)\n",
    "        self.canvas.bind(\"<ButtonRelease-1>\", self.on_mouse_up)\n",
    "\n",
    "    def change_language(self):\n",
    "        \"\"\"切换语言\"\"\"\n",
    "        # 把language.json文件中的current_language的值改为当前的self.current_language\n",
    "        self.current_language = self.language_var.get()  # 获取所选择的语言\n",
    "        utils.write_language_data(language_data, \"current_language\", self.current_language)\n",
    "        logging.info(f\"当前语言已切换为{self.current_language}。\")\n",
    "        confirm = messagebox.askyesno(language_data[self.current_language]['confirm_restart'],\n",
    "                                  language_data[self.current_language]['restart_warning'])\n",
    "        if confirm:\n",
    "            self.master.destroy()\n",
    "            # os.execl(sys.executable, sys.executable, *sys.argv)\n",
    "            subprocess.Popen([sys.executable] + sys.argv)\n",
    "\n",
    "    def merge_channels(self):\n",
    "        \"\"\"合并多个单通道图像为一个多通道图像\"\"\"\n",
    "        # 打开文件对话框,让用户选择多个图像文件\n",
    "        file_paths = filedialog.askopenfilenames()\n",
    "        # 如果用户没有选择文件,直接返回\n",
    "        if not file_paths:\n",
    "            return\n",
    "        \n",
    "        # 如果只选择了一个文件,显示错误信息并返回\n",
    "        if len(file_paths) == 1:\n",
    "            tk.messagebox.showerror(language_data[self.current_language]['error'], language_data[self.current_language]['single_file_error'])\n",
    "            return\n",
    "        \n",
    "        try:\n",
    "            self.image = image_channels.merge_image_channels(file_paths)\n",
    "        except ValueError as e:\n",
    "            tk.messagebox.showerror(language_data[self.current_language]['error'], str(e))\n",
    "            return\n",
    "        \n",
    "        # 显示当前图像\n",
    "        self.display_image()\n",
    "\n",
    "    def split_image_channels(self):\n",
    "        \"\"\"分割当前图像的通道,并在新窗口中显示每个通道\"\"\"\n",
    "        if self.image is None:\n",
    "            tk.messagebox.showinfo(language_data[self.current_language]['error'], language_data[self.current_language]['no_image_loaded'])\n",
    "            return\n",
    "\n",
    "        if len(self.image.shape) == 2:\n",
    "            tk.messagebox.showinfo(language_data[self.current_language]['error'], language_data[self.current_language]['single_channel_error'])\n",
    "            return\n",
    "\n",
    "        try:\n",
    "            channels = image_channels.split_image_channels(self.image)\n",
    "        except ValueError as e:\n",
    "            tk.messagebox.showinfo(language_data[self.current_language]['error'], str(e))\n",
    "            return\n",
    "        \n",
    "        for i, channel in enumerate(channels):\n",
    "            window = tk.Toplevel(self.master)\n",
    "            window.title(language_data[self.current_language]['channel_title'].format(i))\n",
    "            canvas = tk.Canvas(window, width=300, height=300)\n",
    "            canvas.pack()\n",
    "\n",
    "            # 定义内部函数用于保存单个通道\n",
    "            def save_channel(channel, index):\n",
    "                save_path = filedialog.asksaveasfilename(defaultextension=config.DEFAULT_SAVE_FORMAT)\n",
    "                if not save_path:\n",
    "                    return\n",
    "                image_io.save_image(channel, save_path)\n",
    "                tk.messagebox.showinfo(language_data[self.current_language]['success'], language_data[self.current_language]['channel_saved'].format(index, save_path))\n",
    "            \n",
    "            save_button = tk.Button(window, text=language_data[self.current_language]['save_channel'], command=lambda channel=channel: save_channel(channel, i))\n",
    "            save_button.pack()\n",
    "\n",
    "            window.update()\n",
    "\n",
    "            # 显示子通道图像\n",
    "            image = Image.fromarray(channel)  # 将通道数据转换为PIL图像对象\n",
    "            canvas_width = canvas.winfo_width()  # 获取画布的宽度\n",
    "            canvas_height = canvas.winfo_height()  # 获取画布的高度\n",
    "            image_width, image_height = image.size  # 获取图像的宽度和高度\n",
    "            scale = min(canvas_width / image_width, canvas_height / image_height)  # 计算图像的缩放比例\n",
    "            if scale < 1:  # 如果图像过大,缩小图像\n",
    "                new_width = int(image_width * scale)\n",
    "                new_height = int(image_height * scale)\n",
    "                image = image.resize((new_width, new_height), Image.LANCZOS)  # 使用Lanczos滤波器进行缩小\n",
    "            else:  # 如果图像过小,放大图像\n",
    "                new_width = int(image_width * scale)\n",
    "                new_height = int(image_height * scale)\n",
    "                image = image.resize((new_width, new_height), Image.BICUBIC)  # 使用双三次滤波器进行放大\n",
    "\n",
    "            photo = ImageTk.PhotoImage(image)  # 将PIL图像对象转换为PhotoImage对象\n",
    "            x = (canvas_width - new_width) // 2  # 计算图像在画布上的水平居中位置\n",
    "            y = (canvas_height - new_height) // 2  # 计算图像在画布上的垂直居中位置\n",
    "            canvas.create_image(x, y, anchor=tk.NW, image=photo)  # 在画布上显示图像\n",
    "            canvas.image = photo  # 将PhotoImage对象保存到画布中,防止被垃圾回收\n",
    "\n",
    "            # 在子图上添加清晰的标题\n",
    "            title = f\"Channel {i}\"\n",
    "            canvas.create_text(canvas_width//2, 15, text=title, font=(\"Arial\", 15), fill=\"black\")\n",
    "\n",
    "        logging.info(\"图像分割操作成功。\")  # 记录成功日志\n",
    "        \n",
    "    def update_brightness(self, value):\n",
    "        \"\"\"更新亮度显示（当滑块被拖动时调用）\"\"\"\n",
    "        # 你可以在这里添加实时预览功能\n",
    "        pass\n",
    "\n",
    "    def show_image_details(self):\n",
    "        \"\"\"显示当前图像的详细信息\"\"\"\n",
    "        if self.image is None:  # 如果当前没有图像,显示相应消息并返回\n",
    "            messagebox.showinfo(language_data[self.current_language]['error'], language_data[self.current_language]['no_image_loaded'])\n",
    "            logging.info(\"没有载入任何图像,无法显示图像细节。\")  # 记录日志信息\n",
    "            return\n",
    "\n",
    "        # 从路径中提取文件名\n",
    "        filename = os.path.basename(self.image_path)\n",
    "\n",
    "        # 计算其他基本信息\n",
    "        total_pixels = self.image.size  # 获取图像的总像素数\n",
    "        shape = self.image.shape  # 获取图像的形状(高度,宽度,通道数)\n",
    "        channels = 1 if len(self.image.shape) == 2 else self.image.shape[2]  # 获取图像的通道数\n",
    "        dtype = str(self.image.dtype)  # 获取图像像素的数据类型\n",
    "\n",
    "        # 构建信息字符串,包括文件名\n",
    "        details = language_data[self.current_language]['info_str'].format(filename,total_pixels,shape,channels,dtype)\n",
    "\n",
    "        # 显示信息\n",
    "        messagebox.showinfo(\"INFO\", details)\n",
    "        logging.info(\"已显示图像细节。\")  # 记录成功日志\n",
    "\n",
    "    def on_mouse_down(self, event):\n",
    "        \"\"\"处理鼠标左键按下事件\"\"\"\n",
    "        if self.current_rect and not self.is_drawing:\n",
    "            # 如果存在矩形框且不是在拖动状态,删除矩形框并重置状态\n",
    "            self.canvas.delete(self.current_rect)\n",
    "            self.current_rect = None\n",
    "        else:\n",
    "            # 否则,开始绘制新的矩形框\n",
    "            self.rect_start_x = event.x\n",
    "            self.rect_start_y = event.y\n",
    "            self.is_drawing = True\n",
    "\n",
    "    def on_mouse_move(self, event):\n",
    "        \"\"\"处理鼠标左键移动事件\"\"\"\n",
    "        if self.is_drawing:\n",
    "            if self.current_rect:\n",
    "                self.canvas.delete(self.current_rect)\n",
    "            self.current_rect = self.canvas.create_rectangle(self.rect_start_x, self.rect_start_y, event.x, event.y, outline=\"red\")\n",
    "\n",
    "    def on_mouse_up(self, event):\n",
    "        \"\"\"处理鼠标左键释放事件\"\"\"\n",
    "        # 标记矩形的结束点并完成绘制\n",
    "        self.rect_end_x, self.rect_end_y = event.x, event.y\n",
    "        self.is_drawing = False\n",
    "        # 保存裁剪区域的坐标\n",
    "        self.crop_region = (min(self.rect_start_y, self.rect_end_y), max(self.rect_start_y, self.rect_end_y),\n",
    "                            min(self.rect_start_x, self.rect_end_x), max(self.rect_start_x, self.rect_end_x))\n",
    "        logging.info(\"已绘制裁剪区域。\")  # 记录成功日志\n",
    "\n",
    "    def Open_as_original(self):\n",
    "        \"\"\"打开原始图像\"\"\"\n",
    "        logging.info(\"正在打开原始图像...\") \n",
    "        self.image_path = filedialog.askopenfilename()  # 打开文件对话框,让用户选择图像文件\n",
    "        if not self.image_path:\n",
    "            logging.info(\"没有选择任何图像文件,打开操作已取消。\")  # 记录日志信息\n",
    "            return  # 如果没有选择任何文件,直接返回\n",
    "        self.image = image_io.load_image(self.image_path)  # 加载图像文件\n",
    "        self.display_image()  # 显示图像\n",
    "        logging.info(\"成功打开原始图像。\")  # 记录成功日志\n",
    "    \n",
    "    def Open_as_gray(self):\n",
    "        \"\"\"将原始图像转换为灰度图像\"\"\"\n",
    "        self.image_path = filedialog.askopenfilename()  # 打开文件对话框,让用户选择图像文件\n",
    "        if not self.image_path:\n",
    "            logging.info(\"没有选择任何图像文件,打开操作已取消。\")  # 记录日志信息\n",
    "            return  # 如果没有选择任何文件,直接返回\n",
    "        self.image = image_io.load_image(self.image_path, as_gray=True)  # 加载图像文件并转换为灰度图像\n",
    "        self.display_image()  # 显示图像\n",
    "        logging.info(\"成功打开并转换为灰度图像。\")  # 记录成功日志\n",
    "\n",
    "    def display_image(self):\n",
    "        \"\"\"在主画布上显示当前图像\"\"\"\n",
    "        if self.image is None:\n",
    "            logging.info(\"当前没有图像可显示。\")  # 记录日志信息\n",
    "            return  # 如果当前没有图像,直接返回\n",
    "\n",
    "        image = Image.fromarray(self.image)  # 将numpy数组转换为PIL图像对象\n",
    "\n",
    "        # 计算图像的缩放比例\n",
    "        canvas_width = self.canvas.winfo_width()  # 获取画布的宽度\n",
    "        canvas_height = self.canvas.winfo_height()  # 获取画布的高度\n",
    "        image_width, image_height = image.size  # 获取图像的宽度和高度\n",
    "        scale = min(canvas_width / image_width, canvas_height / image_height)  # 计算图像的缩放比例\n",
    "\n",
    "        # 缩放图像\n",
    "        if scale < 1:\n",
    "            # 图像过大,缩小图像\n",
    "            new_width = int(image_width * scale)\n",
    "            new_height = int(image_height * scale)\n",
    "            image = image.resize((new_width, new_height), Image.LANCZOS)  # 使用Lanczos滤波器进行缩小\n",
    "        else:\n",
    "            # 图像过小,放大图像\n",
    "            new_width = int(image_width * scale)\n",
    "            new_height = int(image_height * scale)\n",
    "            image = image.resize((new_width, new_height), Image.BICUBIC)  # 使用双三次滤波器进行放大\n",
    "\n",
    "        photo = ImageTk.PhotoImage(image)  # 将PIL图像对象转换为PhotoImage对象\n",
    "        self.canvas.delete(\"all\")  # 清除画布上的所有内容\n",
    "\n",
    "        # 计算图像在画布上的居中位置\n",
    "        x = (canvas_width - new_width) // 2\n",
    "        y = (canvas_height - new_height) // 2\n",
    "        self.canvas.create_image(x, y, anchor=tk.NW, image=photo)  # 在画布上显示图像, tk.NW表示图像的左上角对齐画布的左上角\n",
    "        self.canvas.image = photo  # 将PhotoImage对象保存到画布中,防止被垃圾回收\n",
    "        logging.info(\"成功显示图像。\")  # 记录成功日志\n",
    "\n",
    "    def save_image(self):\n",
    "        \"\"\"保存当前图像\"\"\"\n",
    "        if self.image is None:\n",
    "            tk.messagebox.showinfo(language_data[self.current_language]['error'], language_data[self.current_language]['no_image_to_be_saved'])\n",
    "            logging.error(\"没有图像可保存,保存操作已取消。\")  # 记录错误日志\n",
    "            return  # 如果当前没有图像,直接返回\n",
    "\n",
    "        save_path = filedialog.asksaveasfilename(defaultextension=config.DEFAULT_SAVE_FORMAT)  # 打开保存文件对话框\n",
    "        if not save_path:\n",
    "            logging.info(\"没有选择保存路径,保存操作已取消。\")  # 记录日志信息\n",
    "            return  # 如果没有选择保存路径,直接返回\n",
    "\n",
    "        image_io.save_image(self.image, save_path)  # 将当前图像保存到指定路径\n",
    "        logging.info(f\"图像已成功保存到 {save_path}。\")  # 记录成功日志\n",
    "\n",
    "    def apply_brightness(self, brightness_percent):\n",
    "        \"\"\"应用亮度调整\"\"\"\n",
    "        if self.image is None:\n",
    "            return\n",
    "            \n",
    "        # 将百分比转换为亮度因子\n",
    "        brightness_factor = brightness_percent / 100.0\n",
    "        \n",
    "        # 调整图像亮度\n",
    "        if len(self.image.shape) == 3:  # 彩色图像\n",
    "            # 对于RGB图像，分别调整每个通道\n",
    "            adjusted_image = np.clip(self.image * brightness_factor, 0, 255).astype(self.image.dtype)\n",
    "        else:  # 灰度图像\n",
    "            # 对于灰度图像，直接调整\n",
    "            adjusted_image = np.clip(self.image * brightness_factor, 0, 255).astype(self.image.dtype)\n",
    "            \n",
    "        # 更新当前图像并显示\n",
    "        self.image = adjusted_image\n",
    "        self.display_image()\n",
    "        \n",
    "        # 关闭亮度调节窗口\n",
    "        if hasattr(self, 'brightness_window'):\n",
    "            self.brightness_window.destroy()\n",
    "            \n",
    "        logging.info(f\"图像亮度已调整为{brightness_percent}%\")\n",
    "\n",
    "    def show_move_diff(self):\n",
    "        \"\"\"显示移动差分边缘检测结果\"\"\"\n",
    "        if self.image is None:\n",
    "            messagebox.showinfo(language_data[self.current_language]['error'], \n",
    "                               language_data[self.current_language]['no_image_loaded'])\n",
    "            return\n",
    "            \n",
    "        if len(self.image.shape) != 2:\n",
    "            messagebox.showinfo(language_data[self.current_language]['error'], \n",
    "                               language_data[self.current_language]['need_gray_image'])\n",
    "            return\n",
    "            \n",
    "        # 创建差分窗口\n",
    "        self.diff_window = tk.Toplevel(self.master)\n",
    "        self.diff_window.title(language_data[self.current_language]['move_diff_title'])\n",
    "        \n",
    "        # 创建平移量滑块\n",
    "        self.shift_scale = tk.Scale(self.diff_window, from_=-3, to=3, orient=tk.HORIZONTAL,\n",
    "                                   label=language_data[self.current_language]['shift_amount'],\n",
    "                                   resolution=1, length=300)\n",
    "        self.shift_scale.set(0)\n",
    "        self.shift_scale.pack(padx=20, pady=10)\n",
    "        \n",
    "        # 创建应用按钮\n",
    "        apply_button = tk.Button(self.diff_window, \n",
    "                                text=language_data[self.current_language]['apply'],\n",
    "                                command=self.apply_move_diff)\n",
    "        apply_button.pack(pady=10)\n",
    "        \n",
    "        # 创建取消按钮\n",
    "        cancel_button = tk.Button(self.diff_window, \n",
    "                                 text=language_data[self.current_language]['cancel'],\n",
    "                                 command=self.close_diff_window)\n",
    "        cancel_button.pack(pady=5)\n",
    "        \n",
    "        # 创建图像显示区域\n",
    "        self.diff_canvas = tk.Canvas(self.diff_window, width=600, height=400)\n",
    "        self.diff_canvas.pack()\n",
    "\n",
    "        self.apply_move_diff()\n",
    "\n",
    "    def apply_move_diff(self):\n",
    "        \"\"\"应用移动差分计算\"\"\"\n",
    "        # 修正：先检查self.image是否为None，再检查其形状\n",
    "        if self.image is None or len(self.image.shape) != 2:\n",
    "            return\n",
    "            \n",
    "        shift = self.shift_scale.get()  # 获取当前滑块值\n",
    "        # 计算平移后的图像\n",
    "        shifted_image = np.roll(self.image, shift=shift, axis=1)\n",
    "        \n",
    "        # 计算差分并归一化\n",
    "        diff_image = np.abs(self.image - shifted_image).astype(np.float32)\n",
    "        if diff_image.max() > 0:\n",
    "            diff_image = (diff_image / diff_image.max() * 255).astype(np.uint8)\n",
    "            \n",
    "        # 显示差分结果\n",
    "        self.display_diff_image(diff_image)\n",
    "\n",
    "    def display_diff_image(self, image_array):\n",
    "        \"\"\"在差分窗口显示图像\"\"\"\n",
    "        if image_array is None:\n",
    "            return\n",
    "            \n",
    "        image = Image.fromarray(image_array)\n",
    "        canvas_width = self.diff_canvas.winfo_width()\n",
    "        canvas_height = self.diff_canvas.winfo_height()\n",
    "        \n",
    "        # 计算缩放比例\n",
    "        image_width, image_height = image.size\n",
    "        scale = min(canvas_width / image_width, canvas_height / image_height)\n",
    "        \n",
    "        # 缩放图像\n",
    "        if scale < 1:\n",
    "            image = image.resize((int(image_width * scale), int(image_height * scale)), Image.LANCZOS)\n",
    "        else:\n",
    "            image = image.resize((int(image_width * scale), int(image_height * scale)), Image.BICUBIC)\n",
    "            \n",
    "        # 显示图像\n",
    "        photo = ImageTk.PhotoImage(image)\n",
    "        self.diff_canvas.delete(\"all\")\n",
    "        x = (canvas_width - image.width) // 2\n",
    "        y = (canvas_height - image.height) // 2\n",
    "        self.diff_canvas.create_image(x, y, anchor=tk.NW, image=photo)\n",
    "        self.diff_canvas.image = photo\n",
    "\n",
    "    def close_diff_window(self):\n",
    "        \"\"\"关闭差分窗口\"\"\"\n",
    "        if hasattr(self, 'diff_window') and self.diff_window:\n",
    "            self.diff_window.destroy()\n",
    "            self.diff_window = None\n",
    "\n",
    "    # 新增掩模相关方法\n",
    "    def create_mask(self, color_channel):\n",
    "        \"\"\"创建单色通道掩模\"\"\"\n",
    "        if self.image is None:\n",
    "            messagebox.showinfo(language_data[self.current_language]['error'], \n",
    "                               language_data[self.current_language]['no_image_loaded'])\n",
    "            return\n",
    "            \n",
    "        if len(self.image.shape) != 3 or self.image.shape[2] != 3:\n",
    "            messagebox.showinfo(language_data[self.current_language]['error'], \n",
    "                               \"需要打开RGB彩色图像\")\n",
    "            return\n",
    "            \n",
    "        # 提取指定颜色通道作为掩模\n",
    "        mask = self.image[:, :, color_channel]\n",
    "        self.show_mask_window(mask, color_channel)\n",
    "\n",
    "    def show_mask_window(self, mask, color_channel):\n",
    "        \"\"\"显示掩模窗口并提供保存功能\"\"\"\n",
    "        self.mask_window = tk.Toplevel(self.master)\n",
    "        self.mask_window.title(f\"{language_data[self.current_language]['mask']} - {self.get_color_name(color_channel)}\")\n",
    "        \n",
    "        # 显示掩模图像\n",
    "        image = Image.fromarray(mask)\n",
    "        canvas = tk.Canvas(self.mask_window, width=300, height=300)\n",
    "        canvas.pack()\n",
    "\n",
    "        # 计算缩放比例\n",
    "        canvas_width = canvas.winfo_width()\n",
    "        canvas_height = canvas.winfo_height()\n",
    "        image_width, image_height = image.size\n",
    "        scale = min(canvas_width / image_width, canvas_height / image_height)\n",
    "        if scale < 1:\n",
    "            image = image.resize((int(image_width*scale), int(image_height*scale)), Image.LANCZOS)\n",
    "        else:\n",
    "            image = image.resize((int(image_width*scale), int(image_height*scale)), Image.BICUBIC)\n",
    "        \n",
    "        photo = ImageTk.PhotoImage(image)\n",
    "        canvas.create_image((canvas_width//2, canvas_height//2), image=photo, anchor=tk.CENTER)\n",
    "        canvas.image = photo\n",
    "\n",
    "        # 创建保存按钮\n",
    "        save_button = tk.Button(self.mask_window, text=language_data[self.current_language]['save_channel'],\n",
    "                               command=lambda: self.save_mask(mask, color_channel))\n",
    "        save_button.pack(pady=10)\n",
    "\n",
    "    def save_mask(self, mask, color_channel):\n",
    "        \"\"\"保存掩模图像\"\"\"\n",
    "        save_path = filedialog.asksaveasfilename(defaultextension=config.DEFAULT_SAVE_FORMAT,\n",
    "                                                filetypes=[(\"PNG Files\", \"*.png\"), (\"JPEG Files\", \"*.jpg\")])\n",
    "        if not save_path:\n",
    "            return\n",
    "            \n",
    "        # 转换为8位灰度图像保存\n",
    "        mask_8bit = mask.astype(np.uint8)\n",
    "        try:\n",
    "            image_io.save_image(mask_8bit, save_path)\n",
    "            messagebox.showinfo(language_data[self.current_language]['success'],\n",
    "                               language_data[self.current_language]['channel_saved'].format(\n",
    "                                   self.get_color_name(color_channel), save_path))\n",
    "        except Exception as e:\n",
    "            messagebox.showerror(language_data[self.current_language]['error'], f\"保存失败: {str(e)}\")\n",
    "\n",
    "    def get_color_name(self, channel_index):\n",
    "        \"\"\"获取通道名称\"\"\"\n",
    "        return {\n",
    "            0: language_data[self.current_language]['red_mask'],\n",
    "            1: language_data[self.current_language]['green_mask'],\n",
    "            2: language_data[self.current_language]['blue_mask']\n",
    "        }.get(channel_index, \"未知\")\n",
    "\n",
    "# 创建主窗口和应用程序实例\n",
    "root = tk.Tk()\n",
    "app = ImageProcessor(root)\n",
    "root.mainloop()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "642fc6c1-6fa7-44a6-bb97-e6dbc6d299f5",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
